#include "SKBookLibrary.hpp"
#include "SKThread.hpp"
#include <ostream>
#include <fstream>

bool SKBookLibrary::initialize()
{
    if (!skThread_.initialize())
    {
	cerr << " Error initializing the SKThread" << endl;
	return false;
    }

    return true;
}

bool SKBookLibrary::AddBook(SKBookNode* book)
{
    if (book)
    {
	// need to do all the manipulation under the accessLock
	skThread_.writerLock();

	// will create a copy of the book in the library
	// first check if already present
	SKBookStoreMap<BookId, SKBookNode>::iterator iter = bookStoreMap_.findNode(book->bookId_);

	if (iter == bookStoreMap_.end())
	{
	    // create now
	    bookStoreMap_[book->bookId_] = *book;
	    authorMap_[book->authorName_].addNode(&bookStoreMap_[book->bookId_]);
	    categoryMap_[book->category_].addNode(&bookStoreMap_[book->bookId_]);

	    // check notification map for any existing notifications to be sent out
	    SKBookStoreMap<BookId, FPTR>::iterator notificationIter = notificationMap_.findNode(book->bookId_);
	    if (notificationIter != notificationMap_.end())
	    {
		notificationIter->value_(book->bookId_);
		// delete the notification once sent
		notificationMap_.deleteNode(notificationIter);
	    }

	    skThread_.writerUnlock();
	    return true;
	}
	else
	{
	    cerr << "Book [" << book->bookId_ << "] already exists" << endl;
	}

	skThread_.writerUnlock();
    } 

    return false;
}

bool SKBookLibrary::removeBook(BookId bookId)
{
    // take the accessLock to delete the node
    skThread_.writerLock();

    SKBookStoreMap<BookId, SKBookNode>::iterator iter = bookStoreMap_.findNode(bookId);

    if (iter == bookStoreMap_.end())
    {
	cerr << "Book [" << bookId << "] is not found" << endl;
	skThread_.writerUnlock();
	return false;
    }

    // First delete from authorMap and categoryMap and then from the store
    authorMap_[iter->value_.authorName_].deleteNode(&iter->value_);
    categoryMap_[iter->value_.category_].deleteNode(&iter->value_);

    bookStoreMap_.deleteNode(iter);

    skThread_.writerUnlock();
    return true;
}

bool SKBookLibrary::isAvailableBookId(BookId bookId)
{
    bool isAvailable = false;

    skThread_.readerLock();
    SKBookStoreMap<BookId, SKBookNode>::iterator iter = bookStoreMap_.findNode(bookId);

    if (iter != bookStoreMap_.end())
    {
	isAvailable = true;
    }

    skThread_.readerUnlock();

    return isAvailable;
}

void SKBookLibrary::notifyOnAvailability(BookId bookId, FPTR notifyCallback)
{
    skThread_.readerLock();
    // first check if available .. if it is no need to register the callback
    SKBookStoreMap<BookId, SKBookNode>::iterator iter = bookStoreMap_.findNode(bookId);

    if ((iter != bookStoreMap_.end()) && iter->value_.isAvailable_)
    {
	// book already available... give the callback now and don't register it
	notifyCallback(bookId);
	skThread_.readerUnlock();
	return;
    }

    skThread_.readerUnlock();

    skThread_.writerLock();
    // register the callback for the bookId
    notificationMap_[bookId] = notifyCallback;
    skThread_.writerUnlock();
}

SKBookNode* SKBookLibrary::getBook(BookId bookId)
{
    skThread_.readerLock();
    SKBookStoreMap<BookId, SKBookNode>::iterator iter = bookStoreMap_.findNode(bookId);

    if (iter != bookStoreMap_.end())
    {
	// mark availability as false since we are lending the book
	iter->value_.isAvailable_ = false;

	// if notifications is set for the book .. send the notification out.
	SKBookStoreMap<BookId, FPTR>::iterator notificationIter = notificationMap_.findNode(bookId);
	if (notificationIter != notificationMap_.end())
	{
	    notificationIter->value_(bookId);
	    // delete the notification once sent
	    notificationMap_.deleteNode(notificationIter);
	}

	skThread_.readerUnlock();
	return &iter->value_;
    }

    skThread_.readerUnlock();
    return 0;
}

void SKBookLibrary::collectBook(BookId bookId)
{
    skThread_.readerLock();
    SKBookStoreMap<BookId, SKBookNode>::iterator iter = bookStoreMap_.findNode(bookId);

    if (iter != bookStoreMap_.end())
    {
	iter->value_.isAvailable_ = true;
    }
    else
    {
	cerr << "Invalid Book [" << bookId << "] received" << endl;
    }
    skThread_.readerUnlock();
}

void SKBookLibrary::displayBooksByAuthor(const std::string& authorName)
{
    skThread_.readerLock();
    SKBookStoreMap<std::string, SKBookList>::iterator iter = authorMap_.findNode(authorName);

    if (iter == authorMap_.end())
    {
	cerr << "No books found for author [" << authorName << "]" << endl;
	skThread_.readerUnlock();
	return;
    }

    SKBookList::iterator authorIter = iter->value_.begin();

    cout << "Following books available for author [" << authorName << "]" << endl;
    for ( ; authorIter != iter->value_.end(); ++authorIter)
    {
	cout << authorIter->node_->bookId_ << endl;
    }
    skThread_.readerUnlock();
}

void SKBookLibrary::displayBooksByCategory(const std::string& category)
{
    skThread_.readerLock();
    SKBookStoreMap<std::string, SKBookList>::iterator iter = categoryMap_.findNode(category);

    if (iter == categoryMap_.end())
    {
	cerr << "No books found for author [" << category << "]" << endl;
	skThread_.readerUnlock();
	return;
    }

    SKBookList::iterator categoryIter = iter->value_.begin();

    cout << "Following books available for category [" << category << "]" << endl;
    for ( ; categoryIter != iter->value_.end(); ++categoryIter)
    {
	cout << categoryIter->node_->bookId_ << endl;
    }
    skThread_.readerUnlock();
}

void SKBookLibrary::showAllBooks()
{
    skThread_.readerLock();
    SKBookStoreMap<BookId, SKBookNode>::iterator iter = bookStoreMap_.begin();

    for ( ; iter != bookStoreMap_.end(); ++iter)
    {
	cout << iter->value_.bookId_ << endl;
	cout << iter->value_.authorName_ << endl;
    }
    skThread_.readerUnlock();
}
    
